package com.only4play.practice.invoice.core.facade;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.nacos.shaded.com.google.common.collect.Streams;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.only4play.codegen.support.exception.BusinessException;
import com.only4play.practice.common.enums.invoice.InvoiceStyle;
import com.only4play.practice.common.enums.invoice.InvoiceType;
import com.only4play.practice.invoice.api.model.vo.EnterpriseEntityVO;
import com.only4play.practice.invoice.core.config.InvoiceOperateProperties;
import com.only4play.practice.invoice.core.domainservice.model.exchange.InvoiceItemModel;
import com.only4play.practice.invoice.core.domainservice.model.exchange.InvoiceModel;
import com.only4play.practice.invoice.core.domainservice.model.exchange.OperateUserModel;
import com.only4play.practice.invoice.core.domainservice.model.exchange.SellerModel;
import com.only4play.practice.invoice.core.domainservice.model.register.OrderRegisterResultModel;
import com.only4play.practice.invoice.core.domainservice.pipeline.context.ExchangeInvoiceContext;
import com.only4play.practice.invoice.core.enums.InvoiceErrorCode;
import com.only4play.practice.common.enums.ValidStatus;
import com.only4play.practice.common.other.CodeValue;
import com.only4play.practice.invoice.api.model.resp.EnterpriseEntityResponse;
import com.only4play.practice.invoice.api.model.resp.PayItemConfigResponse;
import com.only4play.practice.invoice.api.model.resp.TaxRateConfigResponse;
import com.only4play.practice.invoice.core.entity.EnterpriseRouter;
import com.only4play.practice.invoice.core.entity.PayItemConfig;
import com.only4play.practice.invoice.core.entity.TaxRateConfig;
import com.only4play.practice.invoice.core.service.IEnterpriseEntityService;
import com.only4play.practice.invoice.core.service.IEnterpriseRouterService;
import com.only4play.practice.invoice.core.service.IPayItemConfigService;
import com.only4play.practice.invoice.core.service.ITaxRateConfigService;
import com.only4play.practice.invoice.elastic.OrderReceiptDocument;
import io.vavr.API;
import io.vavr.Tuple2;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static io.vavr.API.$;
import static io.vavr.API.Match;

@Service
@RequiredArgsConstructor
public class InvoiceFacadeServiceImpl implements IInvoiceFacadeService {

    private final IEnterpriseRouterService enterpriseRouterService;
    private final IEnterpriseEntityService enterpriseEntityService;
    private final IPayItemConfigService payItemConfigService;
    private final ITaxRateConfigService taxRateConfigService;

    private final InvoiceOperateProperties operateProperties;

  /**
   * 一般公司也不会有太多的企业开票主体,太多就加缓存
   *
   * @param orderAttr
   * @return
   */
  @Override
  public EnterpriseEntityResponse orderRouter2TaxCode(List<CodeValue> orderAttr) {
    //根据排序匹配到最优路由
    List<RouterCheckerModel> models = orderAttr.stream()
        .map(v -> new RouterCheckerModel(v.getK(), v.getV())).collect(Collectors.toList());
      Optional<EnterpriseRouter> enterpriseRouter =
              enterpriseRouterService.list()
        .stream()
        .filter(r -> Objects.equals(ValidStatus.VALID, r.getValidStatus()))
        .sorted(Comparator.comparingInt(EnterpriseRouter::getSortNum))
        .filter(router -> new HashSet<>(models).containsAll(
            router.getCodeValueList()
                .stream()
                .map(v -> new RouterCheckerModel(v.getK(), v.getV()))
                .collect(Collectors.toList())))
        .findFirst();
    if (enterpriseRouter.isPresent()) {
        enterpriseEntityService.findById(
                enterpriseRouter.get().getEnterpriseId());
        return new EnterpriseEntityResponse();
    } else {
      throw new BusinessException(InvoiceErrorCode.ENTERPRISE_NOT_FIND);
    }
  }

  @Override
  public List<PayItemConfigResponse> getPayItemConfigs(String tradeTypeCode) {
     // List<PayItemConfig> configs = payItemConfigRepository.findPayItemConfigsByTradeTypeCode(
     //     tradeTypeCode);
      List<PayItemConfig> configs =
              payItemConfigService.findPayItemConfigsByTradeTypeCode(tradeTypeCode);
      // 将数据库实体转换为Response
      return configs.stream().map(payItemConfig -> {
        PayItemConfigResponse payItemConfigResponse = new PayItemConfigResponse();
        BeanUtil.copyProperties(payItemConfig,payItemConfigResponse);
        return payItemConfigResponse;
      }).collect(Collectors.toList());
  }


  @Override
  public Optional<TaxRateConfigResponse> findRateConfigByTaxCategoryCode(String taxCategoryCode) {
    TaxRateConfig taxRate =
            taxRateConfigService.findByTaxCategoryCode(
        taxCategoryCode);
    if(ObjectUtil.isNotNull(taxRate)){
      TaxRateConfigResponse taxRateConfigResponse = new TaxRateConfigResponse();
      BeanUtil.copyProperties(taxRate,taxRateConfigResponse);
      return Optional.of(taxRateConfigResponse);
    }
    return Optional.empty();
  }

    /**
     * 根据配置的票面最大金额拆票
     *
     * @param context
     */
    @Override
    public List<Tuple2<String, Map<Integer, List<OrderReceiptDocument>>>> splitInvoice(
            ExchangeInvoiceContext context) {
        //查询到所有的凭据【之前注册订单的发票凭据时会存一份到 es 中，这里取出来】
        Iterable<OrderReceiptDocument> all = receiptDocumentRepository.findAllById(
                context.getExchangeInvoiceModel().getFlowNos().stream().map(f -> String.valueOf(f)).collect(
                        Collectors.toList()));

        InvoiceType invoiceType = context.getExchangeInvoiceModel().getInvoiceType();
        //根据企业的税务编码进行分组
        Map<String, List<OrderReceiptDocument>> enterpriseMap = null ;
                Streams.stream(all)
                .collect(Collectors.groupingBy(doc -> doc.getTaxCode()));
        //企业的税务编码 -> 第几张发票 -> 发票对应的所有凭据信息 【可能有多个企业，使用Tuple2元组划分】
        List<Tuple2<String, Map<Integer, List<OrderReceiptDocument>>>> list = Lists.newArrayList();
        //根据配置的票面金额 然后算出发票索引号，然后分组
        enterpriseMap.entrySet().stream().forEach(m -> {
            //记录当前企业票面总额，用于计算发票索引号,需要排序是为了尽量让所有的票 依次累加
            Map<String, BigDecimal> totalAmount = Maps.newConcurrentMap();
            Map<Integer, List<OrderReceiptDocument>> invoiceMap = m.getValue()
                    .stream()
                    // 先排个序
                    .sorted(Comparator.comparing(OrderReceiptDocument::getValidAmount))
                    .collect(Collectors.groupingBy(
                            //
                            doc -> getIndexKey(m.getKey(), doc.getValidAmount(), totalAmount, invoiceType)));
            list.add(new Tuple2<>(m.getKey(), invoiceMap));
        });
        return list;
    }

    @Override
    public void populateInvoice(ExchangeInvoiceContext context) {
        //获取拆票结果
        List<Tuple2<String, Map<Integer, List<OrderReceiptDocument>>>> splitResult = context.getSplitResult();
        if (IterUtil.isEmpty(splitResult)) {
            throw new BusinessException(InvoiceErrorCode.SPLIT_RESULT_EMPTY);
        }
        List<InvoiceModel> invoiceModels = Lists.newArrayList();
        splitResult.stream()
                .forEach(tp -> {
                    //税务编码
                    String taxCode = tp._1;
                    //发票信息
                    Map<Integer, List<OrderReceiptDocument>> invoiceInfo = tp._2;
                    //查询企业信息
                    EnterpriseEntityVO vo = enterpriseEntityService.findByTaxCode(taxCode);
                    InvoiceModel im = new InvoiceModel();
                    //销方信息即企业信息
                    SellerModel sm = vo2SellerModel(vo);
                    im.setSellerModel(sm);
                    //设置操作人信息
                    im.setOperateUserModel(getOperateUserModel());
                    //获取开票方式
                    InvoiceStyle invoiceStyle = context.getExchangeInvoiceModel().getInvoiceStyle();
                    List<InvoiceItemModel> itemModels = Lists.newArrayList();
                    invoiceInfo.entrySet().forEach(info -> {
                        im.setIndexNo(info.getKey());
                        im.setReceiptDocuments(info.getValue());
                        List<OrderRegisterResultModel.ReceiptItemModel> allModels = info.getValue()
                                .stream()
                                .map(m -> m.getReceiptItemModels())
                                .flatMap(p -> p.stream())
                                .collect(Collectors.toList());
                        //根据条目或者类别
                        Map<String, List<OrderRegisterResultModel.ReceiptItemModel>> itemMap;
                        if (Objects.equals(InvoiceStyle.CATEGORY, invoiceStyle)) {
                            //如果按类别开票 则根据类别分组计算求和
                            itemMap = allModels.stream()
                                    .collect(Collectors.groupingBy(OrderRegisterResultModel.ReceiptItemModel::getTaxCategoryCode));
                        } else {
                            //如果按商品开票，则根据商品分组计算求和
                            itemMap = allModels.stream()
                                    .collect(Collectors.groupingBy(r -> String.valueOf(r.getSkuId())));
                        }
                        AtomicInteger lineCode = new AtomicInteger(0);
                        itemMap.entrySet()
                                .forEach(ey -> {
                                    InvoiceItemModel model = new InvoiceItemModel();
                                    //数量求和
                                    int totalCount = ey.getValue().stream().mapToInt(v -> v.getCount())
                                            .reduce(0, (a, b) -> (a + b));
                                    model.setCount(totalCount);
                                    BigDecimal totalAmount = ey.getValue().stream().map(v -> v.getAmount())
                                            .reduce(BigDecimal.ZERO, (a, b) -> NumberUtil.add(a, b));
                                    model.setAmount(totalAmount);
                                    //金额求和
                                    //根据类别的话可以随便找个第一个商品set
                                    OrderRegisterResultModel.ReceiptItemModel first = ey.getValue().get(0);
                                    model.setDisplayName(first.getDisplayName());
                                    model.setItemUnit(first.getItemUnit());
                                    model.setSkuId(first.getSkuId());
                                    model.setTaxRate(first.getTaxRate());
                                    model.setSkuName(first.getSkuName());
                                    model.setTaxCategory(first.getTaxCategory());
                                    model.setTaxCategoryCode(first.getTaxCategoryCode());
                                    //金税要求设置一下行号
                                    model.setLineCode(lineCode.incrementAndGet());
                                    itemModels.add(model);
                                });
                    });
                    im.setItemList(itemModels);
                    invoiceModels.add(im);
                });
        context.getResultModel().setInvoiceList(invoiceModels);
    }

    /**
     * 开票人信息
     *
     * @return
     */
    private OperateUserModel getOperateUserModel() {
        OperateUserModel model = new OperateUserModel();
        // 一般都是固定的，可以放在配置文件中
        model.setDrawer(operateProperties.getDrawer());
        model.setPayee(operateProperties.getPayee());
        model.setReviewer(operateProperties.getReviewer());
        return model;
    }

    /**
     * 组装销方信息
     *
     * @param vo
     * @return
     */
    private SellerModel vo2SellerModel(EnterpriseEntityVO vo) {
        SellerModel sm = new SellerModel();
        sm.setSellerAccount(vo.getBankAccount());
        sm.setSellerAddress(vo.getRegisterAddress());
        sm.setSellerName(vo.getEnterpriseName());
        sm.setSellerPhone(vo.getRegisterPhone());
        sm.setSellerTaxNo(vo.getTaxNo());
        sm.setSellerOpenBank(vo.getOpenBank());
        return sm;
    }

    /**
     * 获取发票的索引编号  这里可能出现一个订单拆成多个发票的问题，这里一般情况下遇不到，可以考虑
     *
     * @param taxCode
     * @param amount
     * @param totalAmount
     * @param invoiceType
     * @return
     */
    private Integer getIndexKey(String taxCode, BigDecimal amount,
                                Map<String, BigDecimal> totalAmount, InvoiceType invoiceType) {
        BigDecimal max = getMaxMoneyConfig(invoiceType, taxCode);
        BigDecimal old = totalAmount.getOrDefault(taxCode, new BigDecimal(0));
        BigDecimal addResult = NumberUtil.add(old, amount);
        totalAmount.put(taxCode, addResult);
        return (addResult.intValue() / max.intValue()) + 1;
    }

    /**
     * 根据企业税号获取配置的最大限额
     *
     * @param invoiceType
     * @param taxCode
     * @return
     */
    private BigDecimal getMaxMoneyConfig(InvoiceType invoiceType, String taxCode) {
        EnterpriseEntityVO vo = enterpriseEntityService.findByTaxCode(taxCode);
        Number number = Match(invoiceType).of(
                API.Case($(InvoiceType.GENERAL_ELECTRONIC), vo.getElectronicInvoiceMaxLimit()),
                API.Case($(InvoiceType.SPECIAL), vo.getSpecialInvoiceMaxLimit()),
                API.Case($(InvoiceType.GENERAL_PAPER), vo.getGeneralInvoiceMaxLimit())
        );
        BigDecimal max = new BigDecimal(String.valueOf(number));
        return max;
    }

    @Value
    class RouterCheckerModel {

        private String code;
        private String value;
    }

}
